#include "PageReplacementManager.h"

PageReplacementManager::PageReplacementManager(int pSize, int kPages, int uPages, Memory* MM, Memory* disk, const int uAlgo, const int kAlgo, const int numOfEntries, MemoryManagementUnit* mmu, PCBS* processes, int clock)
{
	m_PageSize = pSize;
	m_MM = MM;
	m_MM->setPRM(this);
	m_disk = disk;
	m_disk->setPRM(this);
	m_UserAlgo = uAlgo;
	m_KernelAlgo = kAlgo;
	m_processes = processes;
	m_Map = new int*[2];
	m_Map[USER] = new int[uPages];
	m_Map[KERNEL] = new int[kPages];
	m_NumOfEntries = numOfEntries;
	m_mmu = mmu;
	m_numOfKernelPagesInMM = kPages;
	m_numOfUserPagesInMM = uPages;
	fifo = new list<int>*[2];
	fifo[USER] = new list<int>();
	fifo[KERNEL] = new list<int>();
	clockHands = new int[2];
	clockHands[USER] = 0;
	clockHands[KERNEL] = 0;
	WSClockDelta = clock;
}

PageReplacementManager::~PageReplacementManager(void)
{
	delete[] m_Map;
	delete[] fifo;
	delete[] clockHands;
}

int PageReplacementManager::loadUserPage(int processID, int pageNumber)
{
	PCB* requested = (*m_processes)[processID];
	int diskPageIndex = requested->userPageStart + pageNumber;
	int mmIndex = m_MM->allocate(USER, pageNumber, processID);
	if(mmIndex == -1)
	{
		if( m_UserAlgo == FIFO)
		{
			mmIndex = FIFOReplace(USER);
		}
		else
		{
			mmIndex = WSClockReplace(USER);
		}
		int pid = m_MM->m_pages[USER][mmIndex].pid;
		int id = m_MM->m_pages[USER][mmIndex].id / m_NumOfEntries;
		//Page* replaced = &m_disk->m_pages[KERNEL][m_Map[KERNEL][mmIndex]];
		//updateMap(replaced->id, (*m_processes)[replaced->pid],-1);
		updateMap(id, (*m_processes)[pid],-1);
		writeToDisk(USER, mmIndex);
	}
	m_MM->replace(USER,mmIndex,&m_disk->m_pages[USER][diskPageIndex]);
	fifo[USER]->push_back(mmIndex);
	updateMap(pageNumber, requested, mmIndex);
	return mmIndex;
}

void PageReplacementManager::writeToDisk(int place, int mmIndex)
{
	Page* p = &m_MM->m_pages[place][mmIndex];
	if(p->dirty)
	{
		PCB* tmp = (*m_processes)[p->pid];
		int diskIndex;
		if(place == USER)
		{
			diskIndex = tmp->userPageStart + p->id;
		}
		else
		{
			diskIndex = tmp->kernelPageStart + p->id;
		}

		for(int i=0 ; i<m_PageSize ; i++)
		{
			m_disk->m_pages[place][diskIndex].data[i] = p->data[i];
		}
		m_disk->m_pages[place][diskIndex].reference = p->reference;
		m_disk->m_pages[place][diskIndex].referenceTime = p->referenceTime;
		m_disk->m_pages[place][diskIndex].dirty = p->dirty;
		m_disk->m_pages[place][diskIndex].id = p->id;
		m_disk->m_pages[place][diskIndex].pid = p->pid;
	}
}

int PageReplacementManager::loadKernelPage(int processID, int pageNumber)
{
	PCB* requested = (*m_processes)[processID];
	int diskPageIndex = requested->kernelPageStart + pageNumber;
	int mmIndex = m_MM->allocate(KERNEL, pageNumber, processID);
	if(mmIndex == -1)
	{
		if( m_KernelAlgo == FIFO)
		{
			mmIndex = FIFOReplace(KERNEL);
		}
		else
		{
			mmIndex = WSClockReplace(KERNEL);
		}
		int replacedPid = m_MM->m_pages[KERNEL][mmIndex].pid;
		int pageID = m_MM->m_pages[KERNEL][mmIndex].id;
		m_mmu->replaceOuter(replacedPid, pageID, -1);
		writeToDisk(KERNEL, mmIndex);
	}
	m_MM->replace(KERNEL,mmIndex,&m_disk->m_pages[KERNEL][diskPageIndex]);
	fifo[KERNEL]->push_back(mmIndex);	
	m_Map[KERNEL][mmIndex] = diskPageIndex;
	m_mmu->replaceOuter(processID, pageNumber, mmIndex);
	return mmIndex;
}

int PageReplacementManager::FIFOReplace(int place)
{
	int index = fifo[place]->front();
	fifo[place]->pop_front();
	return index;
}

int PageReplacementManager::WSClockReplace(int place)
{
	int selected = -1;
	int first = clockHands[place];
	int pages = (place == USER ? m_numOfUserPagesInMM : m_numOfKernelPagesInMM);
	int biggestDelta = first;

	while(selected < 0)
	{
		int pageNum = clockHands[place];
		Page* p = &m_MM->m_pages[place][pageNum];
		int rp = p->referenceTime;
		int pid = p->pid;
		int t = (*m_processes)[pid]->virtualTime;
		int currentDelta = t - rp;

		Page* tmp = &m_MM->m_pages[place][biggestDelta];
		int time = (*m_processes)[tmp->pid]->virtualTime;

		if((time - tmp->referenceTime) < currentDelta) biggestDelta = pageNum;

		if(p->reference)
		{
			p->reference = false;
			p->referenceTime = t;
		}
		else if(currentDelta <= WSClockDelta)
		{
		}
		else if(currentDelta > WSClockDelta)
		{
			selected = pageNum;
		}

		clockHands[place] = (clockHands[place] + 1) %  pages;
		
		if(clockHands[place] == first)
		{
			selected = biggestDelta;
		}
	}

	return selected;
}

void PageReplacementManager::updateMap(int pageNumber, PCB* requested, int data)
{
	int kernelDiskIndex = pageNumber/m_NumOfEntries + requested->kernelPageStart;
	bool kernelInMM = false;
	int kernelMMIndex = -1;
	for(int i=0 ; i<m_numOfKernelPagesInMM && !kernelInMM ; i++)
	{
		kernelMMIndex = i;
		kernelInMM = (m_Map[KERNEL][i] == kernelDiskIndex);
	}
	if(!kernelInMM)	
		kernelMMIndex = loadKernelPage(requested->pid, pageNumber/m_NumOfEntries);
	
	m_MM->write(KERNEL, kernelMMIndex, (pageNumber % m_NumOfEntries) * 4, (byte*)&data, 4, requested->virtualTime); 
}

void PageReplacementManager::removeFIFO(int place, int mmIndex)
{
	fifo[place]->remove(mmIndex);
}
